package org.apache.commons.graph.export

import com.gitee.wsl.io.write
import com.gitee.wsl.io.writeText
import com.gitee.wsl.text.format.format
import kotlinx.io.Buffer
import kotlinx.io.Sink
import kotlinx.io.readString
import org.apache.commons.graph.DirectedGraph
import org.apache.commons.graph.Graph
import org.apache.commons.graph.Mapper
import kotlin.reflect.KClass

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

/**
 * This class is NOT thread-safe!
 *
 * @param <V>
 * @param <E>
</E></V> */
class DotExporter<V, E>(
    graph: Graph<V, E>,
    name: String?
) : AbstractExporter<V, E, DotExporter<V, E>>(graph, name) {

    private val vertexIdentifiers: Map<V, Int>

    private var printWriter: Sink? = null

    private var connector: String? = null

    init {
        this.vertexIdentifiers = generateVertexIdentifiers(graph)
    }

    @Throws(Exception::class)
    override fun comment(text: String) {
        printWriter!!.write(text)
    }

    @Throws(Exception::class)
    protected override fun edge(edge: E, head: V, tail: V, properties: Map<String, Any>?) {
        printWriter!!.write("  ${vertexIdentifiers[head]} $connector ${vertexIdentifiers[tail]}")

        printVertexOrEdgeProperties(properties?: emptyMap())
    }

    @Throws(Exception::class)
    override fun endGraph() {
        printWriter!!.write('}'.code)
    }

    @Throws(Exception::class)
    override fun endSerialization() {
        // do nothing?
    }

    @Throws(Exception::class)
    override fun enlistEdgesProperty(name: String?, type: KClass<*>?) {
        // not needed in DOT
    }

    @Throws(Exception::class)
    override fun enlistVerticesProperty(name: String?, type: KClass<*>?) {
        // not needed in DOT
    }

    private fun generateVertexIdentifiers(graph: Graph<V, E>): Map<V, Int> {
        val vertexIdentifiers: MutableMap<V, Int> = HashMap()
        var count = 1

        for (vertex in graph.vertices) {
            vertexIdentifiers[vertex] = count++
        }

        return vertexIdentifiers
    }

    private fun printVertexOrEdgeProperties(properties: Map<String, Any>) {
        if (properties.isNotEmpty()) {
            var countAddedProperties = 0
            printWriter!!.write(" [")

            for ((key, value) in properties) {
                val formattedString = if (countAddedProperties == properties.size - 1) "$key=\"$value\"" else "$key=\"$value\" "
                printWriter!!.format(formattedString)
                countAddedProperties++
            }

            printWriter!!.format("];%n")
        }
    }

    @Throws(Exception::class)
    override fun startGraph(name: String?) {
        val graphDeclaration: String

        if (graph is DirectedGraph<*, *>) {
            graphDeclaration = DIGRAPH
            connector = DICONNECTOR
        } else {
            graphDeclaration = GRAPH
            connector = CONNECTOR
        }

        printWriter!!.format("$graphDeclaration $name {%n")
    }

    @Throws(Exception::class)
    override fun startSerialization() {
        printWriter = writer!!
    }

    @Throws(Exception::class)
    override fun vertex(vertex: V, properties: Map<String, Any>?) {
        printWriter!!.format("  %s", vertexIdentifiers[vertex])

        printVertexOrEdgeProperties(properties?: emptyMap())
    }

    fun withEdgeLabels(edgeLabels: Mapper<E, String>): DotExporter<V, E> {
        super.addEdgeProperty(LABEL, edgeLabels)
        return this
    }

    fun <N : Number?> withEdgeWeights(edgeWeights: Mapper<E, N>): DotExporter<V, E> {
        super.addEdgeProperty(WEIGHT, edgeWeights)
        return this
    }

    fun withVertexLabels(vertexLabels: Mapper<V, String>): DotExporter<V, E> {
        super.addVertexProperty(LABEL, vertexLabels)
        return this
    }

    companion object {
        private const val GRAPH = "graph"

        private const val DIGRAPH = "digraph"

        private const val CONNECTOR = "--"

        private const val DICONNECTOR = "->"

        private const val WEIGHT = "weight"

        private const val LABEL = "label"
    }
}

internal fun Sink.format(string: String, vararg data:Any?) = writeText(String.format(string,data))

fun<V, E> Graph<V, E>.exportDotString(name: String?=null):String {
    val buffer=Buffer()
    DotExporter(this,name).to(buffer)
    return buffer.readString()
}